package parser;

import java.io.*;
import java.util.ArrayList;
import executor.ExecutionTree;
import token.Token;
import token.Token.TokenType;
import lexer.*;

public class Parser {

	private Lexer lexer;
	private int errorCount;
	private Token tokenInQueue;
	private ExecutionTree tree;
	private boolean switchLevel;

	/*
	 * TODO:
	 *  checar lo de que, que pasa si sobra un caracter
	 */

	public Parser(Lexer lexer) throws IOException {
		this.lexer = lexer;
		errorCount = 0;
		tokenInQueue = null;
		tree = null;
		parse();
		System.out.println(tree.getRoot());
	}

	//NO TERMINALES

	//PROGRAMS AND BLOCKS

	private void parse() throws IOException { 
		programHeading();
		block();
		match(TokenType.DOT);
	}

	private void programHeading() throws IOException {			//READY
		match(TokenType.PROGRAM);
		match(TokenType.IDENTIFIER);
		if(lambdaMatch(TokenType.O_PAREN)) {
			identifierList();
			match(TokenType.C_PAREN);
		}
		match(TokenType.SEMICOLON);
	}

	private void block() throws IOException {					//READY
		declarationPart();
		statementPart();
	}

	private void declarationPart() throws IOException {			//READY
		labelDeclarationPart();
		constantDefinitionPart();
		typeDefinitionPart();
		variableDeclarationPart();
		procedureAndFunctionDeclarationPart();
	}

	private void labelDeclarationPart() throws IOException {	//READY
		if(lambdaMatch(TokenType.LABEL)) {
			do {
				label();
			} while(lambdaMatch(TokenType.COMMA));
			match(TokenType.SEMICOLON);
		}
	}

	private void constantDefinitionPart() throws IOException {	//READY
		if(lambdaMatch(TokenType.CONST)) {
			do {
				constantDefinition();
				match(TokenType.SEMICOLON);
				tokenInQueue = lexer.nextToken();	
				/*
				 * the line above allows us to check if the next token is an Identifier, if it is, that means that there's 
				 * (or ought to be) another constant, in any case the next char remains on the instance tokenInQueue to be checked latter
				 */
			} while(tokenInQueue.getType() == TokenType.IDENTIFIER);
		}
	}

	private void constantDefinition() throws IOException {	//READY
		Token tokenToCheck = checkNextToken();	
		String key = tokenToCheck.getValue();
		match(TokenType.IDENTIFIER);
		match(TokenType.EQUALS_SIGN);
		constant();
		lexer.addToConstants(key, tokenToCheck);
	}

	private void typeDefinitionPart() throws IOException {	//READY
		if(lambdaMatch(TokenType.TYPE)) {
			do {
				typeDefinition();
				match(TokenType.SEMICOLON);
				tokenInQueue = lexer.nextToken();	
				/*
				 * the line above allows us to check if the next token is an Identifier, if it is, that means that there's 
				 * (or ought to be) another constant, in any case the next char remains on the instance tokenInQueue to be checked latter
				 */
			} while(tokenInQueue.getType() == TokenType.IDENTIFIER);
		}
	}

	private void typeDefinition() throws IOException {			//READY
		Token tokenToCheck = checkNextToken();	
		String key = tokenToCheck.getValue();
		match(TokenType.IDENTIFIER);
		match(TokenType.EQUALS_SIGN);
		type();
		lexer.addToTypes(key, tokenToCheck);
	}

	private void variableDeclarationPart() throws IOException {	//READY
		if (lambdaMatch(TokenType.VAR)) {
			do{
				variableDeclaration();
				match(TokenType.SEMICOLON);
			}while(checkNextToken().getType() == TokenType.IDENTIFIER);	//check if next token is an identifier, that means that the next tokens should match whit a variableDeclaration()
		}
	}

	private void variableDeclaration() throws IOException {		//READY
		ArrayList<Token> identifiers = identifierList();
		match(TokenType.COLON);		
		lexer.addToVariables(identifiers, type()); 
	}

	private void procedureAndFunctionDeclarationPart() throws IOException {		//READY
		do {
			checkNextToken();		//load next token in tokenInQueue
			if (tokenInQueue.getType() == TokenType.PROCEDURE) {
				procedureDeclaration();
			}else if(tokenInQueue.getType() == TokenType.FUNCTION) {
				functionDeclaration();
			}else {
				return;
			}
			match(TokenType.SEMICOLON);
			checkNextToken();		//load next token in tokenInQueue
		}while((tokenInQueue.getType() == TokenType.PROCEDURE) || (tokenInQueue.getType() == TokenType.FUNCTION));
	}

	private void procedureDeclaration() throws IOException {		//READY	
		match(TokenType.PROCEDURE);
		checkNextToken();		//load next token in tokenInQueue
		if (tokenInQueue.getType() == TokenType.PROCEDURE_IDENTIFIER) {
			procedureIdentification(true);
			match(TokenType.SEMICOLON);
			procedureBody();
		}else {
			procedureHeading(true);
			match(TokenType.SEMICOLON);
			checkNextToken();		//load next token in tokenInQueue
			if (tokenInQueue.getType() == TokenType.FORWARD) {
				directive();
			}else {
				procedureBody();
			}
		}
	}

	private void procedureBody() throws IOException {				//READY		
		block();
	}

	private void functionDeclaration() throws IOException {			//READY		
		match(TokenType.FUNCTION);
		checkNextToken();		//load next token in tokenInQueue
		if (tokenInQueue.getType() == TokenType.FUNCTION_IDENTIFIER) {
			functionIdentification(true);
			match(TokenType.SEMICOLON);
			functionBody();
		}else {
			functionHeading(true);
			match(TokenType.SEMICOLON);
			checkNextToken();		//load next token in tokenInQueue
			if (tokenInQueue.getType() == TokenType.FORWARD) {
				directive();
			}else {
				functionBody();
			}
		}
	}

	private void functionBody() throws IOException {		//READY		
		block();
	}

	private void directive() throws IOException {		
		match(TokenType.FORWARD);
		//the compiler-defined-directives is missing
	}

	private void statementPart() throws IOException {		//READY
		match(TokenType.BEGIN);
		statementSequence();
		match(TokenType.END);
	}

	//PROCEDURE AND FUNCTION DEFINITIONS

	private void procedureHeading() throws IOException {							//READY
		procedureHeading(true);
	}

	private void procedureHeading(boolean withoutProcedure) throws IOException {	//READY	
		if (!withoutProcedure) {
			match(TokenType.PROCEDURE);
		}
		match(TokenType.IDENTIFIER);
		if(checkNextToken().getType() == TokenType.O_PAREN) {
			formalParameterList();
		}
	}

	private void functionHeading() throws IOException {							//READY
		functionHeading(true);
	}

	private void functionHeading(boolean withoutFunction) throws IOException {		//READY
		if (!withoutFunction) {
			match(TokenType.FUNCTION);
		}
		match(TokenType.IDENTIFIER);
		if(checkNextToken().getType() == TokenType.O_PAREN) {
			formalParameterList();
		}
		match(TokenType.COLON);
		resultType();
	}

	private void procedureIdentification() throws IOException {							//READY
		procedureIdentification(true);
	}

	private void procedureIdentification(boolean withoutProcedure) throws IOException {				//READY	
		if (!withoutProcedure) {
			match(TokenType.PROCEDURE);
		}
		match(TokenType.PROCEDURE_IDENTIFIER);
	}

	private void functionIdentification() throws IOException {							//READY
		functionIdentification(true);
	}

	private void functionIdentification(boolean withoutFunction) throws IOException {				//READY	
		if (!withoutFunction) {
			match(TokenType.FUNCTION);
		}
		match(TokenType.FUNCTION_IDENTIFIER);
	}

	private void resultType() throws IOException {						//READY
		match(TokenType.TYPE_IDENTIFIER);
	}

	private void formalParameterList() throws IOException {				//READY	
		match(TokenType.O_PAREN);
		do {
			formalParameterSection();
		}while(lambdaMatch(TokenType.SEMICOLON));
		match(TokenType.C_PAREN);
	}

	private void formalParameterSection() throws IOException {			//READY
		checkNextToken();	//load nextToken in tokenInQueue
		if (tokenInQueue.getType() == TokenType.VAR) {
			variableParameterSection();
		}else if (tokenInQueue.getType() == TokenType.PROCEDURE) {
			procedureParameterSection();
		}else if (tokenInQueue.getType() == TokenType.FUNCTION) {
			functionParameterSection();
		}else {
			valueParameterSection();
		}
	}

	private void valueParameterSection() throws IOException {			//READY		
		identifierList();
		match(TokenType.COLON);
		parameterType();
	}

	private void variableParameterSection() throws IOException {		//READY
		match(TokenType.VAR);
		identifierList();
		match(TokenType.COLON);
		parameterType();
	}

	private void procedureParameterSection() throws IOException {		//READY		
		procedureHeading();
	}

	private void functionParameterSection() throws IOException {		//READY		
		functionHeading();
	}

	private void parameterType() throws IOException {					//READY		
		if(!lambdaMatch(TokenType.TYPE_IDENTIFIER)) {
			conformantArraySchema();
		}
	}

	private void conformantArraySchema() throws IOException {			//READY		
		if (checkNextToken().getType() == TokenType.PACKED) {
			packedConformantArraySchema();
		}else {
			unpackedConformantArraySchema();
		}
	}

	private void packedConformantArraySchema() throws IOException {		//READY		
		match(TokenType.PACKED);
		match(TokenType.ARRAY);
		match(TokenType.O_SQBRACKET);
		boundSpecification();
		match(TokenType.C_SQBRACKET);
		match(TokenType.OF);
		match(TokenType.TYPE_IDENTIFIER);
	}

	private void unpackedConformantArraySchema() throws IOException {	//READY
		match(TokenType.ARRAY);
		match(TokenType.O_SQBRACKET);
		do{
			boundSpecification();
		}while(lambdaMatch(TokenType.SEMICOLON));
		match(TokenType.C_SQBRACKET);
		match(TokenType.OF);
		if (!lambdaMatch(TokenType.TYPE_IDENTIFIER)) {
			conformantArraySchema();
		}
	}

	private void boundSpecification() throws IOException {		//READY		
		match(TokenType.IDENTIFIER);
		match(TokenType.DOUBLE_DOT);
		match(TokenType.IDENTIFIER);
		match(TokenType.COLON);
		ordinalTypeIdentifier();
	}

	private void ordinalTypeIdentifier() throws IOException {	//READY		
		match(TokenType.TYPE_IDENTIFIER);
	}

	//STATEMENTS

	private void statementSequence() throws IOException {   //READY 
		do{
			statement();
		}while(lambdaMatch(TokenType.SEMICOLON));
	}

	private void statement() throws IOException {			//READY 
		checkNextToken();	//load nextToken in tokenInQueue
		if (tokenInQueue.getType()==TokenType.PLUS_SIGN || tokenInQueue.getType()==TokenType.MINUS_SIGN || tokenInQueue.getType()==TokenType.UNSIGNED_DIGIT_SEQUENCE) {
			match(TokenType.COLON);
		}
		checkNextToken();	//load nextToken in tokenInQueue
		if (tokenInQueue.getType()==TokenType.BEGIN || tokenInQueue.getType()==TokenType.WHILE || tokenInQueue.getType()==TokenType.REPEAT || tokenInQueue.getType()==TokenType.FOR || tokenInQueue.getType()==TokenType.IF || tokenInQueue.getType()==TokenType.CASE || tokenInQueue.getType()==TokenType.WITH) {
			structuredStatement();
		}else {
			simpleStatement();
		}
	}

	private void simpleStatement() throws IOException { 		//ready
		checkNextToken();	//load nextToken in tokenInQueue
		if(tokenInQueue.getType()==TokenType.GOTO)
		{
			gotoStatement();
		}
		else if(tokenInQueue.getType()==TokenType.PROCEDURE_IDENTIFIER)
		{
			procedureStatement();
		}
		else
		{
			assignmentStatement();
		}
	}

	private void assignmentStatement() throws IOException { //READY
		if(!lambdaMatch(TokenType.FUNCTION_IDENTIFIER))
		{
			variable();
		}
		match(TokenType.COLON_EQUALS);
		expression();
	}

	private void procedureStatement() throws IOException { 
		match(TokenType.PROCEDURE_IDENTIFIER);
		if(checkNextToken().getType()==TokenType.O_PAREN)
		{
			actualParameterList();
		}
	}

	private void gotoStatement() throws IOException {		//READY 
		match(TokenType.GOTO);
		label();
	}

	private void structuredStatement() throws IOException {		//READY	
		checkNextToken();	//load nextToken in tokenInQueue
		if (tokenInQueue.getType() == TokenType.BEGIN) {
			compoundStatement();
		}else if (tokenInQueue.getType() == TokenType.WHILE || tokenInQueue.getType() == TokenType.REPEAT || tokenInQueue.getType() == TokenType.FOR) {
			repetitiveStatement();
		}else if (tokenInQueue.getType() == TokenType.IF || tokenInQueue.getType() == TokenType.CASE) {
			conditionalStatement();
		}else {
			withStatement();
		}
	}

	private void compoundStatement() throws IOException {	//READY 			
		match(TokenType.BEGIN);
		statementSequence();
		match(TokenType.END);
	}

	private void repetitiveStatement() throws IOException {		//READY
		if (tokenInQueue.getType() == TokenType.WHILE) {
			whileStatement();
		}else if (tokenInQueue.getType() == TokenType.REPEAT) {
			repeatStatement();
		}else {
			forStatement();
		}
	}

	private void whileStatement() throws IOException {		//READY
		match(TokenType.WHILE);
		expression();
		match(TokenType.DO);
		statement();
	}

	private void repeatStatement() throws IOException {		//READY		
		match(TokenType.REPEAT);
		statementSequence();
		match(TokenType.UNTIL);
		expression();
	}

	private void forStatement() throws IOException {		//READY			
		match(TokenType.FOR);
		variableIdentifier();
		match(TokenType.COLON_EQUALS);
		initialExpression();
		if(!lambdaMatch(TokenType.TO)) {
			match(TokenType.DOWNTO);
		}
		finalExpression();
		match(TokenType.DO);
		statement();
	}

	private void initialExpression() throws IOException {		//READY		
		expression();
	}

	private void finalExpression() throws IOException {			//READY	
		expression();
	}

	private void conditionalStatement() throws IOException {	//READY			
		if (tokenInQueue.getType() == TokenType.IF) {
			ifStatement();
		}else {
			caseStatement();
		}
	}

	private void ifStatement() throws IOException {		//READY				
		match(TokenType.IF);
		expression();
		match(TokenType.THEN);
		statement();
		if(lambdaMatch(TokenType.ELSE)) {
			statement();
		}
	}

	private void caseStatement() throws IOException {	//READY				
		match(TokenType.CASE);
		expression();
		match(TokenType.OF);
		do {
			caseLimb();
		}while(lambdaMatch(TokenType.SEMICOLON));
		//TODO: { ";" case-limb } [ ";" ]
		match(TokenType.END);
	}

	private void caseLimb() throws IOException {		//READY		
		caseLabelList();
		match(TokenType.COLON);
		statement();
	}

	private void caseLabelList() throws IOException {	//READY		
		do {
			constant();
		}while(lambdaMatch(TokenType.COMMA));
	}

	private void withStatement() throws IOException {	//READY				
		match(TokenType.WITH);
		do {
			match(TokenType.RECORD_VARIABLE);
		}while(lambdaMatch(TokenType.COMMA));
		match(TokenType.DO);
		statement();
	}

	private void actualParameterList() throws IOException {		//READY				
		match(TokenType.O_PAREN);
		do {
			actualParameter();
		}while(lambdaMatch(TokenType.COMMA));
		match(TokenType.C_PAREN);
	}

	private void actualParameter() throws IOException {		//READY		
		checkNextToken();	//load nextToken in tokenInQueue
		if(tokenInQueue.getType()==TokenType.PROCEDURE_IDENTIFIER)
		{
			actualProcedure();
		}
		else if(tokenInQueue.getType()==TokenType.FUNCTION_IDENTIFIER)
		{
			actualFunction();
		}
		else if(tokenInQueue.getType()==TokenType.VARIABLE_IDENTIFIER||tokenInQueue.getType()==TokenType.FIELD_IDENTIFIER||
				tokenInQueue.getType()==TokenType.FILE_VARIABLE||tokenInQueue.getType()==TokenType.ARRAY_VARIABLE||
				tokenInQueue.getType()==TokenType.RECORD_VARIABLE||tokenInQueue.getType()==TokenType.POINTER_VARIABLE)
		{
			actualVariable();
		}
		else
		{
			actualValue();
		}
	}

	private void actualValue() throws IOException {			//READY				
		expression();
	}

	private void actualProcedure() throws IOException {		//READY		
		match(TokenType.PROCEDURE_IDENTIFIER);
	}

	private void actualFunction() throws IOException {		//READY				
		match(TokenType.FUNCTION_IDENTIFIER);
	}

	//EXPRESSIONS

	private void expression() throws IOException {	//READY		
		simpleExpression();
		checkNextToken();	//load nextToken in tokenInQueue
		if (tokenInQueue.getType() == TokenType.EQUALS_SIGN ||
				tokenInQueue.getType() == TokenType.DISTINCT || tokenInQueue.getType() == TokenType.LESS_THAN || 
				tokenInQueue.getType() == TokenType.GREATER_THAN || tokenInQueue.getType() == TokenType.LESSEQUALS_THAN || 
				tokenInQueue.getType() == TokenType.GREATEREQUALS_THAN || tokenInQueue.getType() == TokenType.IN || 
				tokenInQueue.getType() == TokenType.OR || tokenInQueue.getType() == TokenType.DIV || 
				tokenInQueue.getType() == TokenType.MOD || tokenInQueue.getType() == TokenType.AND) {
					relationalOperator();
					simpleExpression();
				}
	}

	private void simpleExpression() throws IOException {		
		sign();
		do {
			term();
		}while(additionOperator());
	}

	private void term() throws IOException {		
		do {
			factor();
		}while(multiplicationOperator());
	}

	private void factor() throws IOException {				//READY
		if (lambdaMatch(TokenType.NIL)) {		//The First call to a lambdaMatch loads the nextToken in tokenInQueue in case it doesn't find the necessary TokenType
			return;
		}else if (lambdaMatch(TokenType.STRING)) {
			return;
		}else if (lambdaMatch(TokenType.NOT)) {
			factor();
			return;
		}else if(tokenInQueue.getType() == TokenType.O_PAREN) {
			lambdaMatch(TokenType.O_PAREN);
			expression();
			lambdaMatch(TokenType.C_PAREN);
		}else if(tokenInQueue.getType() == TokenType.O_SQBRACKET) {
			set();
			return;
		}else if(lambdaMatch(TokenType.CONSTANT_IDENTIFIER)) {
			return;
		}else if(lambdaMatch(TokenType.BOUND_IDENTIFIER)) {
			return;
		}else if(lambdaMatch(TokenType.FUNCTION_IDENTIFIER)) {
			functionDesignator();
			return;
		}else if(tokenInQueue.getType() == TokenType.MINUS_SIGN || checkNextToken().getType() == TokenType.PLUS_SIGN || checkNextToken().getType() == TokenType.UNSIGNED_DIGIT_SEQUENCE) {
			number();
			return;
		}else {
			variable();
		}
	}

	private void relationalOperator() throws IOException {		//READY	
		checkNextToken();	//load nextToken in tokenInQueue
		int initialCount = errorCount;
		switchLevel = true;
		if(tokenInQueue.getType() == TokenType.EQUALS_SIGN) {
			match(TokenType.EQUALS_SIGN);
		}else if(tokenInQueue.getType() == TokenType.DISTINCT) {
			match(TokenType.DISTINCT);
		}else if(tokenInQueue.getType() == TokenType.LESS_THAN) {
			match(TokenType.LESS_THAN);
		}else if(tokenInQueue.getType() == TokenType.GREATER_THAN) {
			match(TokenType.GREATER_THAN);
		}else if(tokenInQueue.getType() == TokenType.LESSEQUALS_THAN) {
			match(TokenType.LESSEQUALS_THAN);
		}else if(tokenInQueue.getType() == TokenType.GREATEREQUALS_THAN) {
			match(TokenType.GREATEREQUALS_THAN);
		}else {
			match(TokenType.IN);
		} 
		if(initialCount != errorCount) {
			switchLevel = false;
		}
	}

	private boolean additionOperator() throws IOException {		//READY	
		if (!sign()) {
			return lambdaMatch(TokenType.OR);
		}
		return true;
	}

	private boolean multiplicationOperator() throws IOException {		//READY	
		checkNextToken();	//load nextToken in tokenInQueue
		if(tokenInQueue.getType() == TokenType.MULTIPLICATION_OPERATOR) {
			match(TokenType.MULTIPLICATION_OPERATOR);
			return true;
		}else if(tokenInQueue.getType() == TokenType.DIVISION_OPERATOR) {
			match(TokenType.DIVISION_OPERATOR);
			return true;
		}else if(tokenInQueue.getType() == TokenType.DIV) {
			match(TokenType.DIV);
			return true;
		}else if(tokenInQueue.getType() == TokenType.MOD) {
			match(TokenType.MOD);
			return true;
		}else if(tokenInQueue.getType() == TokenType.AND) {
			match(TokenType.AND);
			return true;
		}else {
			return false;
		}
	}

	private void variable() throws IOException {				//READY
		checkNextToken();	//load nextToken in tokenInQueue
		if (tokenInQueue.getType() == TokenType.VARIABLE_IDENTIFIER || tokenInQueue.getType() == TokenType.FIELD_IDENTIFIER) {
			entireVariable();
		}else if (tokenInQueue.getType() == TokenType.ARRAY_VARIABLE || tokenInQueue.getType() == TokenType.RECORD_VARIABLE || tokenInQueue.getType() == TokenType.FILE_VARIABLE) {
			componentVariable();
		}else if (!lambdaMatch(TokenType.TYPE_VARIABLE)) {
			referencedVariable();
		}
	}

	private void entireVariable() throws IOException {			//READY			
		if (!lambdaMatch(TokenType.VARIABLE_IDENTIFIER)) {
			match(TokenType.FIELD_IDENTIFIER);
		}
	}

	private void componentVariable() throws IOException {		//READY				
		checkNextToken();	//load nextToken in tokenInQueue
		if (tokenInQueue.getType() == TokenType.ARRAY_VARIABLE) {
			indexedVariable();
		}else if (tokenInQueue.getType() == TokenType.RECORD_VARIABLE) {
			fieldDesignator();
		}else {
			fileBuffer();
		}
	}

	private void indexedVariable() throws IOException {		//READY				
		match(TokenType.ARRAY_VARIABLE);
		match(TokenType.O_SQBRACKET);
		expressionList();
		match(TokenType.C_SQBRACKET);
	}

	private void fieldDesignator() throws IOException {		//READY						
		match(TokenType.RECORD_VARIABLE);
		match(TokenType.DOT);
		match(TokenType.FIELD_IDENTIFIER);
	}

	private void set() throws IOException {					//READY						
		match(TokenType.O_SQBRACKET);
		elementList();
		match(TokenType.C_SQBRACKET);
	}

	private void elementList() throws IOException {		
		checkNextToken();	//load nextToken in tokenInQueue
		if(tokenInQueue.getType()==TokenType.VARIABLE_IDENTIFIER||tokenInQueue.getType()==TokenType.FIELD_IDENTIFIER||
				tokenInQueue.getType()==TokenType.FILE_VARIABLE||tokenInQueue.getType()==TokenType.ARRAY_VARIABLE||
				tokenInQueue.getType()==TokenType.RECORD_VARIABLE||tokenInQueue.getType()==TokenType.POINTER_VARIABLE||
				tokenInQueue.getType()==TokenType.PLUS_SIGN||tokenInQueue.getType()==TokenType.MINUS_SIGN||tokenInQueue.getType()==TokenType.NIL||
				tokenInQueue.getType()==TokenType.STRING||tokenInQueue.getType()==TokenType.NOT||tokenInQueue.getType()==TokenType.O_PAREN||
				tokenInQueue.getType()==TokenType.O_SQBRACKET||tokenInQueue.getType()==TokenType.CONSTANT_IDENTIFIER||
				tokenInQueue.getType()==TokenType.BOUND_IDENTIFIER||tokenInQueue.getType()==TokenType.FUNCTION_IDENTIFIER||
				tokenInQueue.getType()==TokenType.UNSIGNED_DIGIT_SEQUENCE)
		{
			do {
				expression();
			}while(lambdaMatch(TokenType.COMMA));
		}
	}

	private void functionDesignator() throws IOException {		//READY						
		match(TokenType.FUNCTION_IDENTIFIER);
		if (checkNextToken().getType() == TokenType.O_PAREN) {
			actualParameterList();
		}
	}

	private void fileBuffer() throws IOException {			//READY						
		match(TokenType.FILE_VARIABLE);
		match(TokenType.POINTER_TYPE);
	}

	//TYPES

	private Token type() throws IOException {  		//READY
		Token type = checkNextToken();
		if (!lambdaMatch(TokenType.TYPE_IDENTIFIER)) {
			checkNextToken();	//load nextToken in tokenInQueue
			if (tokenInQueue.getType() == TokenType.POINTER_TYPE) {
				return pointerType();
			}else if (tokenInQueue.getType() == TokenType.PACKED || tokenInQueue.getType() == TokenType.ARRAY || tokenInQueue.getType() == TokenType.RECORD || tokenInQueue.getType() == TokenType.SET || tokenInQueue.getType() == TokenType.FILE) {
				return structuredType();
			}else {
				simpleType();
				return null;
			}
		}else {
			return type;
		}
	}

	private void simpleType() throws IOException {  //READY
		if(checkNextToken().getType() == TokenType.O_PAREN) {
			enumeratedType();
		}else {
			subrangeType();
		}
	}

	private void enumeratedType() throws IOException {  //READY
		match(TokenType.O_PAREN);
		identifierList();
		match(TokenType.C_PAREN);
	}

	private void subrangeType() throws IOException {  //READY
		lowerBound();
		match(TokenType.DOUBLE_DOT);
		upperBound();
	}

	private void lowerBound() throws IOException {  //READY
		constant();
	}

	private void upperBound() throws IOException {  //READY
		constant(); 
	}

	private Token structuredType() throws IOException {	//READY  
		lambdaMatch(TokenType.PACKED);
		return unpackedStructuredType();
	}

	private Token unpackedStructuredType() throws IOException {	//READY
		checkNextToken();	//load nextToken in tokenInQueue
		if(tokenInQueue.getType() == TokenType.ARRAY) {
			return arrayType();
		}else if (tokenInQueue.getType() == TokenType.RECORD) {
			recordType();   //TODO hacerlo funcionar
			return null;
		}else if (tokenInQueue.getType() == TokenType.SET) {
			return setType();
		}
		else if (tokenInQueue.getType() == TokenType.FILE) {
			return fileType();     
		}
		return null;
	}

	private Token arrayType() throws IOException {  //READY
		match(TokenType.ARRAY);
		match(TokenType.O_SQBRACKET);
		do {
			indexType();
		}while(lambdaMatch(TokenType.COMMA));
		match(TokenType.C_SQBRACKET);
		match(TokenType.OF);
		return elementType();
	}

	private void indexType() throws IOException {	//READY  
		simpleType();
	}

	private Token elementType() throws IOException {	//READY
		return type();
	}

	private void recordType() throws IOException {	//READY  
		//TODO QUE REGRESE UN TIPO !!!!!
		match(TokenType.RECORD);
		fieldList();
		match(TokenType.END);
	}

	private Token setType() throws IOException {		//READY	
		match(TokenType.SET);
		match(TokenType.OF);
		return baseType();
	}

	private Token baseType() throws IOException {	//READY	
		return type();
	}

	private Token fileType() throws IOException {	//READY
		match(TokenType.FILE);
		match(TokenType.OF);
		return fileComponentType();
	}

	private Token fileComponentType() throws IOException {	//READY	
		return type();
	}

	private Token pointerType() throws IOException {			//READY
		Token type;
		match(TokenType.POINTER_TYPE);
		type = checkNextToken();
		if (match(TokenType.TYPE_IDENTIFIER)) {
			return type;
		}
		return null;
	}

	//RECORD FIELDS

	private void fieldList() throws IOException {			//READY
		checkNextToken();	//load nextToken in tokenInQueue
		if (tokenInQueue.getType() == TokenType.IDENTIFIER) {
			fixedPart();
			lambdaMatch(TokenType.SEMICOLON);
			if(checkNextToken().getType() == TokenType.CASE) {
				variantPart();
				lambdaMatch(TokenType.SEMICOLON);
			}
		}else if (tokenInQueue.getType() == TokenType.CASE) {
			variantPart();
			lambdaMatch(TokenType.SEMICOLON);
		}
	}

	private void fixedPart() throws IOException {		//READY
		do {
			recordSection();
			match(TokenType.SEMICOLON);
		}while(checkNextToken().getType() == TokenType.IDENTIFIER);
	}

	private void recordSection() throws IOException {	//READY
		ArrayList<Token> identifiers = identifierList();
		match(TokenType.COLON);
		lexer.addToIdentifiers(identifiers, type().getType());
	}

	private void variantPart() throws IOException {	
		match(TokenType.CASE);
		tagField();
		match(TokenType.TYPE_IDENTIFIER);
		match(TokenType.OF);
		variant();	
	}

	private void tagField() throws IOException {	//READY
		if(checkNextToken().getType() == TokenType.IDENTIFIER) {
			match(TokenType.IDENTIFIER);
			match(TokenType.COLON);
		}
	}

	private void variant() throws IOException {		//READY	
		caseLabelList();
		match(TokenType.COLON);
		match(TokenType.O_PAREN);
		fieldList();
		match(TokenType.C_PAREN);
	}

	//INPUT/OUTPUT

	private void outputList() throws IOException {		//READY
		do {
			outputValue();
		}while(lambdaMatch(TokenType.COMMA));
	}

	private void outputValue() throws IOException {		//READY
		expression();
		if (lambdaMatch(TokenType.SEMICOLON)) {
			fieldWidth();
			if (lambdaMatch(TokenType.COLON)) {
				fractionLength();
			}
		}
	}

	private void fieldWidth() throws IOException {		//READY
		expression();
	}

	private void fractionLength() throws IOException {	//READY
		expression();
	}

	//VARIABLE AND IDENTIFIER CATEGORIES

	private void fileVariable() throws IOException
	{
		variable();
	}

	private void recordVariable() throws IOException
	{
		variable();
	}

	private void pointerVariable() throws IOException
	{
		variable();
	}

	private void actualVariable() throws IOException
	{
		variable();
	}

	private void arrayVariable() throws IOException
	{
		variable();
	}

	private void variableIdentifier() throws IOException  //READY chito
	{
		match(TokenType.IDENTIFIER);
	}

	private void referencedVariable() throws IOException {		//READY
		match(TokenType.POINTER_VARIABLE);
		match(TokenType.POINTER_TYPE);
	}



	//LOW LEVEL DEFINITIONS

	private void variableList() throws IOException {	//READY 
		do {
			variable();
		}while(lambdaMatch(TokenType.COMMA));
	}

	private ArrayList<Token> identifierList() throws IOException {  //READY
		ArrayList<Token> identifiersList = new ArrayList<Token>();
		Token tokenToAdd;
		do {
			tokenToAdd = checkNextToken();
			if (match(TokenType.IDENTIFIER)) {
				identifiersList.add(tokenToAdd);
			}
		} while(lambdaMatch(TokenType.COMMA));
		return identifiersList;
	}

	private void expressionList() throws IOException {  //READY
		do {
			expression();
		} while(lambdaMatch(TokenType.COMMA));
	}

	private void number() throws IOException {
		integerNumber();
		realNumber(true);
	}

	private void realNumber() throws IOException { //use when you just need to read a real number 
		realNumber(false);
	}

	private void realNumber(boolean fromNumber) throws IOException {	//READY
		/*
		 * if the flag fromNumber is true it means that the first digitSequence has been already read, this flag also means that
		 * this call to this method came from the method from number
		 * 
		 * like the method sign() the purpose of this method is to consume the tokens that belong to a real number
		 */
		if(!fromNumber){
			digitSequence();
		}
		if(lambdaMatch(TokenType.DOT)) {
			if (lambdaMatch(TokenType.UNSIGNED_DIGIT_SEQUENCE)) {
				lambdaMatch(TokenType.SCALE_FACTOR);
			}else {
				match(TokenType.SCALE_FACTOR);
			}
		}else {
			if(fromNumber) {
				lambdaMatch(TokenType.SCALE_FACTOR);
			}else{
				match(TokenType.SCALE_FACTOR);
			}
		}				
	}

	private void integerNumber() throws IOException {	//READY
		digitSequence();
	}

	private void digitSequence() throws IOException {	//READY
		sign();
		match(TokenType.UNSIGNED_DIGIT_SEQUENCE);
	}

	private boolean sign() throws IOException { 		//READY
		/*
		 * The main purpose of this method is to consume the character sign which is frequently optional  
		 * also this method return true in case it find that the read token is MINUS_SIGN or PLUS_SIGN 
		 */
		if(lambdaMatch(TokenType.PLUS_SIGN) || lambdaMatch(TokenType.MINUS_SIGN)) {
			return true;
		}
		return false;
	}

	private void label() throws IOException {			//READY
		integerNumber();
	}

	private void constant() throws IOException {
		if(checkNextToken().getType() != TokenType.STRING) {
			sign();												//if there is, sign is consumed
			if(!lambdaMatch(TokenType.CONSTANT_IDENTIFIER)) {	//if token is not a constant identifier
				number();										//has to be a number
			}
		}else {													//if it was originally a string
			tokenInQueue = null;								//the token String is consumed
		}
	}	

	//TERMINALES

	private boolean match(TokenType expectedTerminal) throws IOException { 		//READY
		Token tokenToCheck = nextToken();	
		if(tokenToCheck.getType() != expectedTerminal) {
			tokenInQueue = tokenToCheck; 
			System.err.println("\nLine: "+lexer.lineRead()+" Token: "+lexer.tokenRead()+"\nexpected: "+expectedTerminal.toString()+"\nReceived token:\n"+tokenToCheck);
			errorCount--;
			return false;
		}
		if(tree == null) {
			tree = new ExecutionTree(tokenToCheck);
		}else /*if(expectedTerminal == TokenType.PLUS_SIGN || expectedTerminal == TokenType.MINUS_SIGN || 
			expectedTerminal == TokenType.MULTIPLICATION_OPERATOR || expectedTerminal == TokenType.DIVISION_OPERATOR || 
			expectedTerminal == TokenType.EQUALS_SIGN || expectedTerminal == TokenType.DISTINCT ||
			expectedTerminal == TokenType.LESS_THAN || expectedTerminal == TokenType.GREATER_THAN ||
			expectedTerminal == TokenType.LESSEQUALS_THAN || expectedTerminal == TokenType.GREATEREQUALS_THAN ||
			expectedTerminal == TokenType.IN || expectedTerminal == TokenType.OR ||
			expectedTerminal == TokenType.DIV || expectedTerminal == TokenType.MOD ||
			expectedTerminal == TokenType.AND) {*/
		if (switchLevel) {
			switchLevel = false;
			ExecutionTree child = tree;
			tree = tree.getLevelUp();
			tree.removeChild(child);
			tree = tree.addChild(tokenToCheck);
			tree.addChild(child,tree);
			tree = tree.getLevelUp();
			tree = tree.getLevelUp();
		}else {
			tree = tree.addChild(tokenToCheck);
		}
		return true;
	}

	private boolean lambdaMatch(TokenType expectedTerminal) throws IOException {	//READY
		Token tokenToCheck = nextToken();	
		if(tokenToCheck.getType() == expectedTerminal) {
			if(tree == null) {
				tree = new ExecutionTree(tokenToCheck);
			}else /*if(expectedTerminal == TokenType.PLUS_SIGN || expectedTerminal == TokenType.MINUS_SIGN || 
					expectedTerminal == TokenType.MULTIPLICATION_OPERATOR || expectedTerminal == TokenType.DIVISION_OPERATOR || 
					expectedTerminal == TokenType.EQUALS_SIGN || expectedTerminal == TokenType.DISTINCT ||
					expectedTerminal == TokenType.LESS_THAN || expectedTerminal == TokenType.GREATER_THAN ||
					expectedTerminal == TokenType.LESSEQUALS_THAN || expectedTerminal == TokenType.GREATEREQUALS_THAN ||
					expectedTerminal == TokenType.IN || expectedTerminal == TokenType.OR ||
					expectedTerminal == TokenType.DIV || expectedTerminal == TokenType.MOD ||
					expectedTerminal == TokenType.AND) {*/
			if (switchLevel) {
				switchLevel = false;
				ExecutionTree child = tree;
				tree = tree.getLevelUp();
				tree.removeChild(child);
				tree = tree.addChild(tokenToCheck);
				tree.addChild(child,tree);
				tree = tree.getLevelUp();
				tree = tree.getLevelUp();
			}else {
				tree = tree.addChild(tokenToCheck);
			}
			return true;
		}
		tokenInQueue = tokenToCheck;
		return false;
	}

	private Token nextToken() throws IOException {
		Token tokenToCheck;	
		if(tokenInQueue != null) {
			tokenToCheck = tokenInQueue;
			tokenInQueue = null;
		}else {
			tokenToCheck = lexer.nextToken();
		}
		return tokenToCheck;
	}

	private Token checkNextToken() throws IOException { 
		/*
		 * this method differs of the method above because this method does not allow the next token to be consumed, 
		 * the next token remains in the instance tokenInQueue
		 */
		Token tokenToCheck;	
		if(tokenInQueue != null) {
			tokenToCheck = tokenInQueue;
		}else {
			tokenToCheck = lexer.nextToken();
			tokenInQueue = tokenToCheck;
		}
		return tokenToCheck;
	}

	@Override
	public String toString() {
		return ""+errorCount;
	}

	public static void main(String[] args) throws IOException {
		Lexer miLexer = new Lexer(new File("/Users/cesaralonsomartinezsanchez/Documents/UMG/Sexto Semestre/Compiladores/Proyecto/CompFinal/src/lexer/Programa.txt"));
		System.err.println(new Parser(miLexer));
	}

}
